#include "config.h"

#include "ms2sim.h"

ecm_double	arrival_rate;
ecm_uint32	duration = 3600;	/* default 1hour */

ecm_double	skew = 0.722;

ecm_uint32	disk_blksize = 512;
ecm_uint64	disk_BW = (30 * 1024 * 1024);
ecm_uint32	disk_seektime;
ecm_uint64	network_BW = (100 * 1024 * 1024);

char	*logbasepath = "ms2sim";
char	*confpath = "ms2sim.conf";

#ifdef MSM_DEBUG
ecm_dbg_t	ms2sim_dbg;
#endif

static void
usage(void)
{
	ecm_fprintf(ecm_stderr,
"Usage: ms2sim [ <options> ]\n"
" Options:\n"
"   -h: help\n"
"   -q: quiet mode\n"
"   -c <conf path>: simulation setup from configuration file\n"
"   -t <duration>: simulation period(hh:mm:ss)\n"
"   -a <ratio>: client arrival ratio(format: <real number>/[h|m|s])\n"
"   -m <mod>: block caching module path\n"
"   -s <size>: block cache pool size\n"
"   -g <dist>: session length distribution.\n"
"        full: every session length is same with media length\n"
"        beta: beta distribution\n"
"   -p <dist>: interseek time distribution.\n"
"        noseek: no seek operation(default)\n"
"        lognormal: interseek time based on lognormal distribution\n"
"   -l <log base name>: base logging file name\n"
"   -x <yes|no>: packet loss not allowed\n"
"   -y <disksim param>: disksim parameter file\n"
#ifdef MSM_DEBUG
"   -d <dbgset>: debug set\n"
"      a: all\n"
"      g: ms2sim\n"
"      r: request\n"
"      S: session\n"
"      s: service\n"
"      d: disk\n"
"      n: network\n"
#endif
	);
}

static void
signal_handler(ecm_signo_t signo)
{
	switch (signo) {
	case ECM_SIGINT:
	case ECM_SIGTERM:
		ECMVERBOSE("Receive termination signal. Trying to exit!");
		going_to_shutdown = ECM_TRUE;
		break;
	default:
		break;
	}
}

#ifdef MSM_DEBUG

static ECMBOOL
parse_debugset(const char *str_dbgset)
{
	ecm_uint32	dbgset = 0;
	const char	*p = str_dbgset;

	while (*p) {
		switch (*p) {
		case 'a':
			dbgset = DBG_ALL;
			break;
		case 'g':
			dbgset |= DBG_MS2SIM;
			break;
		case 'r':
			dbgset |= DBG_REQUEST;
			break;
		case 'S':
			dbgset |= DBG_SESSION;
			break;
		case 's':
			dbgset |= DBG_SERVICE;
			break;
		case 'd':
			dbgset |= DBG_DISK;
			break;
		case 'n':
			dbgset |= DBG_NETWORK;
			break;
		default:
			return ECM_FALSE;
		}
		p++;
	}

	ecm_set_debugset(ms2sim_dbg, dbgset);
	return ECM_TRUE;
}

#endif

static void
init_parse_args(int argc, char *argv[])
{
	int	c;

	while ((c = ecm_getopt(argc, argv, "hqc:t:a:l:m:s:g:p:d:y:x:")) != -1) {
		switch(c) {
		case 'c':
			confpath = ecm_strdup(ecm_optarg);
			if (!ecm_access(confpath, ECM_F_OK)) {
				ERR("%s: does not exist", ecm_optarg);
				ecm_exit(1);
			}
			break;
		default:
			break;
		}
	}
	ecm_getopt_clear();
}

static void
parse_args(int argc, char *argv[])
{
	int	c;

	while ((c = ecm_getopt(argc, argv, "hqc:t:a:l:m:s:g:p:d:y:x:")) != -1) {
		switch(c) {
		case 'h':
			usage();
			ecm_exit(0);
		case 'c':
			break;
		case 'q':
			ecm_set_verbose_mode(ECM_FALSE);
			break;
		case 't':
			if ((duration = msl_parse_duration(ecm_optarg)) == 0) {
				ERR("%s: invalid duration format", ecm_optarg);
				ecm_exit(1);
			}
			break;
		case 'a':
			if (!msl_parse_arrival_rate(ecm_optarg, &arrival_rate)) {
				ERR("%s: invalid arrival rate format", ecm_optarg);
				ecm_exit(1);
			}
			break;
		case 'm':
			bcache_mod = ecm_strdup(ecm_optarg);
			break;
		case 's': {
			ecm_uint64	poolsize;

			if (!msl_parse_bandwidth(ecm_optarg, &poolsize)) {
				ERR("%s: invalid block cache pool size", ecm_optarg);
				ecm_exit(1);
			}
			n_caches = poolsize / cache_blksize;
			break;
		}
		case 'g':
			sesslen_type = msl_sesslen_parse_typestr(ecm_optarg);
			if (sesslen_type == MSL_SESSLEN_TYPE_NONE) {
				ERR("%s: invalid session length distribution");
				ecm_exit(0);
			}
			break;
		case 'p':
			istime_type = msl_istime_parse_typestr(ecm_optarg);
			if (istime_type == MSL_ISTIME_TYPE_NONE) {
				ERR("%s: invalid interseek time distribution");
				ecm_exit(0);
			}
			break;
		case 'y':
			disksim_param = ecm_strdup(ecm_optarg);
			break;
		case 'x':
			if (ecm_sscanf(ecm_optarg, "%lf", &allowed_loss_ratio) != 1) {
				ERR("%s: invalid packet loss ratio", ecm_optarg);
				ecm_exit(1);
			}
			break;
		case 'l':
			if (ecm_empty_str(ecm_optarg)) {
				ERR("empty log base is not allowed");
				ecm_exit(1);
			}
			logbasepath = ecm_strdup(ecm_optarg);
			break;
#ifdef MSM_DEBUG
		case 'd':
			if (!parse_debugset(ecm_optarg)) {
				ERR("invalid debugset");
				ecm_exit(1);
			}
			break;
#endif
		default:
			usage();
			ecm_exit(1);
		}
	}
}

static ECMBOOL
init_ms2sim_pre(void)
{
	if (!init_media_pre() ||
	    !init_conf_pre())
		return ECM_FALSE;
	return ECM_TRUE;
}

static ECMBOOL
init_ms2sim_post(void)
{
	if (!init_bcache() ||
	    !init_media() ||
	    !init_request() ||
	    !init_session() ||
	    !init_disk() ||
	    !init_network() ||
	    !init_disksim() ||
	    !init_stat()) {
		return ECM_FALSE;
	}
	return ECM_TRUE;
}

static ECMBOOL
check_configuration(void)
{
	if (cache_blksize == 0) {
		ERR("cache block size is 0");
		return ECM_FALSE;
	}
	if (n_caches == 0) {
		ERR("n_caches is 0");
		return ECM_FALSE;
	}
	if (cache_blksize < disk_blksize) {
		ERR("cache blksize is smaller than disk blksize");
		return ECM_FALSE;
	}
	if (cache_blksize < disk_blksize) {
		ERR("cache blksize is smaller than disk blksize");
		return ECM_FALSE;
	}
	return ECM_TRUE;
}

int
main(int argc, char *argv[])
{
	msl_initialize();

	ecm_set_output_file(ecm_stderr);
	ecm_set_verbose_mode(ECM_TRUE);

	ecm_signal(ECM_SIGINT, signal_handler);
	ecm_signal(ECM_SIGTERM, signal_handler);

	init_parse_args(argc, argv);
	if (!init_ms2sim_pre()) {
		ERR("init_ms2sim_post() failed");
		return 2;
	}

	if (!check_configuration()) {
		return 3;
	}

#ifdef ECM_DEBUG
	ms2sim_dbg = ecm_create_dbg();
#endif
	parse_args(argc, argv);

	if (!init_ms2sim_post()) {
		ERR("init_ms2sim_post() failed");
		return 4;
	}

	runsim();

	if (allowed_loss_ratio_violated)
		return 127;

	return 0;
}
